'''
Contains the Claim class, which is used to provide common functionality to both
the CSNClaim and NonCSNClaim classes.
'''

import os
import sys
from abc import ABC, abstractmethod
from urllib3.exceptions import (
    NewConnectionError,
    MaxRetryError,
    ProtocolError,
)
import PySimpleGUI as sg
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import TimeoutException, UnexpectedAlertPresentException
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.remote.file_detector import LocalFileDetector
import chromedriver_autoinstaller


class Claim(ABC):
    '''
    Contains the attributes and functions common to both
    the CSNClaim and NonCSNClaim classes.
    '''
    def __init__(self, info):
        sg.theme('Reddit')
        self._info = info
        self._browser = self._get_browser()
        self._wait = WebDriverWait(
            self._browser,
            timeout=90,
            poll_frequency=1
        )
        self._network_exceptions = (
            ConnectionRefusedError,
            NewConnectionError,
            MaxRetryError,
            ConnectionResetError,
            ProtocolError,
            UnexpectedAlertPresentException,
        )
        # To upload the invoice and serial number files
        self._browser.file_detector = LocalFileDetector()

    def quit_browser(self):
        '''Stops the Chromedriver automation.'''
        self._browser.quit()

    def get_network_exceptions(self):
        '''Gets the claim's tuple of network exceptions.'''
        return self._network_exceptions

    @classmethod
    def _get_browser(cls):
        '''
        Chooses the correct version of the ChromeDriver, depending
        on whether the program is being run as a standalone Windows
        executable, or directly with Python.
        '''

        # pylint: disable=protected-access
        # This is needed to access Chrome driver in executable

        driver_name = 'chromedriver.exe'

        try:
            return webdriver.Chrome(
                executable_path=os.path.join(sys._MEIPASS, driver_name)
            )
        except AttributeError: # module 'sys' has no attribute '_MEIPASS'
            chromedriver_autoinstaller.install()
            return webdriver.Chrome()

    @classmethod
    def _get_username(cls):
        '''Displays an input prompt to get the user's HP username.'''
        return sg.popup_get_text(
            'Please enter your HP username',
            title='HP Username',
            keep_on_top=True
        )

    @classmethod
    def _get_password(cls):
        '''Displays an input prompt to get the user's HP password.'''
        return sg.popup_get_text(
            'Please enter your HP password',
            title='HP Password',
            password_char='*',
            keep_on_top=True
        )

    def _check_login_failed(self):
        '''
        Checks if the user has entered an incorrect username or password,
        into the HP warranty website login page.
        '''
        try:
            WebDriverWait(self._browser, 5).until(
                EC.text_to_be_present_in_element(
                    (
                        By.CSS_SELECTOR,
                        '#root > div > div > div.login-password > div > div >'
                        ' div > div.vn-panel.vn-panel--shadow.password_panel >'
                        ' div > div > form > div > div > p'
                    ),
                    'Invalid username or password'
                )
            )
            return True
        except TimeoutException:
            return False

    def _log_into_hp_warranty_site(self):
        '''
        Logs the user into the HP warranty site (either CSN or non-CSN),
        and keeps  prompting them for credentials, if either the username
        or password is incorrect.
        '''
        self._browser.get(self._info['url'])

        username = self._get_username()

        if not username:
            return self._browser.quit()

        password = self._get_password()

        if not password:
            return self._browser.quit()

        username_field = self._wait.until(
            EC.presence_of_element_located( (By.ID, "username") )
        )
        username_field.send_keys(username)
        username_field.submit()

        password_field = self._wait.until(
            EC.presence_of_element_located( (By.ID, "password") )
        )
        password_field.send_keys(password)
        password_field.submit()

        if self._check_login_failed():
            self._log_into_hp_warranty_site()

        self.bypass_hp_page_loading_error()

    def bypass_hp_page_loading_error(self):
        '''
        Clicks the link to go to the home page, if not redirected to the main page,
        after logging in.
        '''
        try:
            self._browser.execute_script(
                'arguments[0].click();',
                WebDriverWait(self._browser, 3).until(
                    EC.element_to_be_clickable(
                        (
                            By.CSS_SELECTOR,
                            'body > blockquote > p:nth-child(3) > a'
                        )
                    )
                )
            )
        except TimeoutException:
            print('The main HP page loaded normally.')

    def _accept_cookies(self):
        '''Clicks the button to accept cookies from the HP website.'''
        try:
            self._browser.execute_script(
                'arguments[0].click();',
                WebDriverWait(self._browser, timeout=30).until(
                    EC.element_to_be_clickable(
                        (By.ID, 'onetrust-accept-btn-handler')
                    )
                )
            )
        except TimeoutException:
            print('The "Accept Cookies" button was not found.')

    @abstractmethod
    def create(self):
        '''Creates a new CSN or non-CSN warranty claim.'''
